/*++

Copyright (c) 1995  Microsoft Corporation

Module Name:

   ioctl.c

Abstract:

    USB device driver for Philips D12 USB test board

Environment:

    kernel mode only

Notes:

  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  PURPOSE.

  Copyright (c) 1996 Microsoft Corporation.  All Rights Reserved.


Revision History:

    5-4-96 : created

--*/

#define DRIVER

#include "wdm.h"
#include "stdarg.h"
#include "stdio.h"

#include "usbdi.h"
#include "usbdlib.h"
#include "D12.h"

#include "ioctl.h"
#include "usbdlib.h"


PUSB_CONFIGURATION_DESCRIPTOR
D12_GetConfigDescriptor(
    IN PDEVICE_OBJECT DeviceObject
    )
/*++

Routine Description:

Arguments:

    DeviceObject - pointer to the device object for this instance of the 82930
                    devcice.


Return Value:

    NT status code

--*/
{
    PDEVICE_EXTENSION deviceExtension;
    NTSTATUS ntStatus;
    PURB urb;
    ULONG siz;
    PUSB_CONFIGURATION_DESCRIPTOR configurationDescriptor = NULL;

    D12_KdPrint (("D12TEST.SYS: enter D12_GetConfigDescriptor\n"));

    deviceExtension = DeviceObject->DeviceExtension;

    urb = ExAllocatePool(NonPagedPool,
                         sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST));

    if (urb) {

        // BUGBUG 82930 chokes if on the next command if you don't get
        // the entire descriptor on the first try

        siz = sizeof(USB_CONFIGURATION_DESCRIPTOR)+256;

get_config_descriptor_retry2:

        configurationDescriptor = ExAllocatePool(NonPagedPool,
                                                 siz);

        if (configurationDescriptor) {

            UsbBuildGetDescriptorRequest(urb,
                                         (USHORT) sizeof (struct _URB_CONTROL_DESCRIPTOR_REQUEST),
                                         USB_CONFIGURATION_DESCRIPTOR_TYPE,
                                         0,
                                         0,
                                         configurationDescriptor,
                                         NULL,
                                         siz,
                                         NULL);

            ntStatus = D12_CallUSBD(DeviceObject, urb);

            D12_KdPrint (("D12TEST.SYS: Configuration Descriptor = %x, len %x\n",
                            configurationDescriptor,
                            urb->UrbControlDescriptorRequest.TransferBufferLength));
        } else {
            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        }

        if (NT_SUCCESS(ntStatus) &&
            (urb->UrbControlDescriptorRequest.TransferBufferLength >= 
             sizeof(USB_CONFIGURATION_DESCRIPTOR)) &&
            (configurationDescriptor->wTotalLength >=
             sizeof(USB_CONFIGURATION_DESCRIPTOR)))
        {
            //
            // The Get Config Descriptor request did not return an error
            // AND at least enough data was transferred to fill a Config
            // Descriptor AND the Config Descriptor wLength is at least the
            // size of a Config Descriptor
            //
            if (configurationDescriptor->wTotalLength > siz)
            {
                //
                // The request buffer is not big enough to hold the
                // entire set of descriptors.  Free the current buffer
                // and retry with a buffer which should be big enough.
                //
                siz = configurationDescriptor->wTotalLength;
                ExFreePool(configurationDescriptor);
                configurationDescriptor = NULL;
                goto get_config_descriptor_retry2;
            }
            else if (configurationDescriptor->wTotalLength >
                     urb->UrbControlDescriptorRequest.TransferBufferLength)
            {
                //
                // The request buffer is greater than or equal to the
                // Config Descriptor wLength, but less data was transferred
                // than wLength.  Return NULL to indicate a device error.
                //
                ExFreePool(configurationDescriptor);
                configurationDescriptor = NULL;
            }
            //
            // else  everything is OK with the Config Descriptor, return it.
            //
        }
        else
        {
            //
            // The Get Config Descriptor request returned an error OR
            // not enough data was transferred to fill a Config Descriptor
            // OR the Config Descriptor wLength is less than the size of
            // a Config Descriptor.  Return NULL to indicate a device error.
            //
            ExFreePool(configurationDescriptor);
            configurationDescriptor = NULL;
        }

        ExFreePool(urb);

    } else {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    }

    D12_KdPrint (("D12TEST.SYS: exit D12_GetConfigDescriptor\n"));

    return configurationDescriptor;
}




NTSTATUS
D12_GetPortStatus(
    IN PDEVICE_OBJECT DeviceObject,
    IN PULONG PortStatus
    )
/*++

Routine Description:

    returns the port status for our device

Arguments:

Return Value:

    STATUS_SUCCESS if successful,
    STATUS_UNSUCCESSFUL otherwise

--*/
{
    NTSTATUS ntStatus, status = STATUS_SUCCESS;
    PIRP irp;
    KEVENT event;
    IO_STATUS_BLOCK ioStatus;
    PIO_STACK_LOCATION nextStack;
    PDEVICE_EXTENSION deviceExtension;

    D12_KdPrint (("D12TEST.SYS: enter D12_GetPortStatus\n"));

    deviceExtension = DeviceObject->DeviceExtension;

    *PortStatus = 0;

    //
    // issue a synchronous request
    //

    KeInitializeEvent(&event, NotificationEvent, FALSE);

    irp = IoBuildDeviceIoControlRequest(
                IOCTL_INTERNAL_USB_GET_PORT_STATUS,
                deviceExtension->TopOfStackDeviceObject,
                NULL,
                0,
                NULL,
                0,
                TRUE, /* INTERNAL */
                &event,
                &ioStatus);

    //
    // Call the class driver to perform the operation.  If the returned status
    // is PENDING, wait for the request to complete.
    //

    nextStack = IoGetNextIrpStackLocation(irp);
    ASSERT(nextStack != NULL);

    nextStack->Parameters.Others.Argument1 = PortStatus;

    D12_KdPrint (("D12TEST.SYS: calling USBD port status api\n"));

    ntStatus = IoCallDriver(deviceExtension->TopOfStackDeviceObject,
                            irp);

    D12_KdPrint (("D12TEST.SYS: return from IoCallDriver USBD %x\n", ntStatus));

    if (ntStatus == STATUS_PENDING) {

        D12_KdPrint (("D12TEST.SYS: Wait for single object\n"));

        status = KeWaitForSingleObject(
                       &event,
                       Suspended,
                       KernelMode,
                       FALSE,
                       NULL);

        D12_KdPrint (("D12TEST.SYS: Wait for single object, returned %x\n", status));
        
    } else {
        ioStatus.Status = ntStatus;
    }

    D12_KdPrint (("D12TEST.SYS: Port status = %x\n", *PortStatus));

    //
    // USBD maps the error code for us
    //
    ntStatus = ioStatus.Status;

    D12_KdPrint (("D12TEST.SYS: D12_GetPortStatus (%x)\n", ntStatus));

    return ntStatus;
}


NTSTATUS
D12_ResetParentPort(
    IN IN PDEVICE_OBJECT DeviceObject
    )
/*++

Routine Description:

    Reset the our parent port

Arguments:

Return Value:

    STATUS_SUCCESS if successful,
    STATUS_UNSUCCESSFUL otherwise

--*/
{
    NTSTATUS ntStatus, status = STATUS_SUCCESS;
    PIRP irp;
    KEVENT event;
    IO_STATUS_BLOCK ioStatus;
    PIO_STACK_LOCATION nextStack;
    PDEVICE_EXTENSION deviceExtension;

    D12_KdPrint (("D12TEST.SYS: enter D12_ResetPort\n"));

    deviceExtension = DeviceObject->DeviceExtension;

    //
    // issue a synchronous request
    //

    KeInitializeEvent(&event, NotificationEvent, FALSE);

    irp = IoBuildDeviceIoControlRequest(
                IOCTL_INTERNAL_USB_RESET_PORT,
                deviceExtension->TopOfStackDeviceObject,
                NULL,
                0,
                NULL,
                0,
                TRUE, /* INTERNAL */
                &event,
                &ioStatus);

    //
    // Call the class driver to perform the operation.  If the returned status
    // is PENDING, wait for the request to complete.
    //

    nextStack = IoGetNextIrpStackLocation(irp);
    ASSERT(nextStack != NULL);

    D12_KdPrint (("D12TEST.SYS: calling USBD enable port api\n"));

    ntStatus = IoCallDriver(deviceExtension->TopOfStackDeviceObject,
                            irp);
                            
    D12_KdPrint (("D12TEST.SYS: return from IoCallDriver USBD %x\n", ntStatus));

    if (ntStatus == STATUS_PENDING) {

        D12_KdPrint (("D12TEST.SYS: Wait for single object\n"));

        status = KeWaitForSingleObject(
                       &event,
                       Suspended,
                       KernelMode,
                       FALSE,
                       NULL);

        D12_KdPrint (("D12TEST.SYS: Wait for single object, returned %x\n", status));
        
    } else {
        ioStatus.Status = ntStatus;
    }

    //
    // USBD maps the error code for us
    //
    ntStatus = ioStatus.Status;

    D12_KdPrint (("D12TEST.SYS: D12_ResetPort (%x)\n", ntStatus));

    return ntStatus;
}


NTSTATUS
D12_ReadWriteRegister(
    IN  PDEVICE_OBJECT DeviceObject,
    IN  PIRP Irp,
    IN  BOOLEAN  bWrite
    )
/*++
Routine Description:
    This function is called for IOCTLs to Read or Write.
    For WRITEs, the data is retrieved from the SystemBuffer and sent to the device.
    
Arguments:
    DeviceObject - pointer to the device object for this instance of the D12 device.
    Irp          - pointer to IRP
    Read         - if TRUE this is a Device-to-Host (Read from device) transfer
                   if FALSE this is a Host-to-Device (Write to device) transfer

Return Value:
    NT status code
        STATUS_SUCCESS:                 Read was done successfully
        STATUS_INVALID_PARAMETER_3:     The Endpoint Index does not specify an IN pipe 
        STATUS_NO_MEMORY:               Insufficient data memory was supplied to perform the READ

    This routine fills the status code into the Irp
    
--*/
{
    USBD_INTERFACE_INFORMATION *    pInterfaceInfo;
    PIO_STACK_LOCATION              irpStack;
    PDEVICE_EXTENSION               deviceExtension;     
    NTSTATUS                        ntStatus;
    PVOID                           ioBuffer;
    ULONG                           length;
    ULONG                           inputBufferLength;
    ULONG                           outputBufferLength;
    ULONG                           siz;
    PURB                            urb;
    USHORT							wValue, wIndex;
	PIO_BLOCK						ioBlock;
	
	D12_KdPrint(("D12TEST.SYS: enter D12_ReadWriteRegister\n"));

    irpStack = IoGetCurrentIrpStackLocation (Irp);
    ASSERT (irpStack != NULL);
    
    deviceExtension = DeviceObject->DeviceExtension;
    ASSERT (deviceExtension != NULL);

    ioBuffer			= Irp->AssociatedIrp.SystemBuffer;
	ioBlock				= (PIO_BLOCK) ioBuffer;
    inputBufferLength	= irpStack->Parameters.DeviceIoControl.InputBufferLength;
    outputBufferLength	= irpStack->Parameters.DeviceIoControl.OutputBufferLength;

    //DEBUG ONLY
    if ((ioBuffer == NULL) || (inputBufferLength == 0) ){
        D12_KdPrint (("D12TEST.SYS: ERROR! ioBuffer %X | inBufLen: %d | outBufLen %d\n",
                          ioBuffer, inputBufferLength, outputBufferLength));
        
        Irp->IoStatus.Information = 0;
        return (STATUS_NO_MEMORY);
        
    } //DEBUG ONLY
        
                          
    siz = sizeof(struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST);

	// allocate urb
	urb = ExAllocatePool(NonPagedPool, siz);

    // By convention, the first byte of the buffer is reserved for the ReportID, so
    // skip over the first Byte when specifying the transferbuffer
	
	length = ioBlock->uLength;
	wValue = (USHORT)ioBlock->uOffset;
	wIndex = (USHORT)ioBlock->uIndex;

    D12_KdPrint (("D12TEST.SYS: ReadWriteRegister: ioBuffer = %p, wIndex = %x, wLength = %lx, wValue = %x.\n",
		ioBlock->pbyData, wIndex, length, wValue));

    if (bWrite) {
        // A WRITE operation implies that the data is gotten from the User's Input Data Buffer
   		UsbBuildVendorRequest(urb,				//ptr to urb
						URB_FUNCTION_VENDOR_DEVICE,
						(USHORT) siz,			//siz of urb
						0,					
						0x0,					//reservedbits=bmRequestType
						0x0C,					//request = USBSCAN IOCTL_WRITE_REGISTER
						wValue,					
						wIndex,				
						ioBlock->pbyData,			//TransferBuffer
						NULL,					//mdl (unused)
						length,					//bufferlength
					    NULL);					//link
    } else {
   		// A READ operation implies that the data is placed in the User's Output Data Buffer
		UsbBuildVendorRequest(urb,				//ptr to urb
						URB_FUNCTION_VENDOR_DEVICE,
						(USHORT) siz,			//siz of urb
						USBD_TRANSFER_DIRECTION_IN,
						0x0,					//reservedbits=bmRequestType
						0x0C,					//request = USBSCAN IOCTL_READ_REGISTER
						wValue,					
						wIndex,				
						ioBuffer,				//TransferBuffer
						NULL,					//mdl (unused)
						length,					//bufferlength
					    NULL);					//link
 }/* else */
	/*
    /*
	// Call the USB Stack. This call blocks until the device returns data.  Note that if the
	// device NAKs forever, this call will not return and this thread will hang.  To correct 
	// this, a timeout value can be specified and a Kernel-Mode timer used to wake 
	// up the driver and then cancel this transfer.  Future versions of this sample 
	// driver will demonstrate that capability, or consult the Windows NT DDK for 
	// further details on the Kernel Timer functionality as well as the 
	// IoCancelIrp functionality.
	*/
	ntStatus = D12_CallUSBD(DeviceObject, urb);

	// The Information field tells IOM how much to copy back into the
    // usermode buffer in the BUFFERED method
    if (NT_SUCCESS(ntStatus) && bWrite) {
        Irp->IoStatus.Information =length;	//sizeof (ULONG);
   
    }else if (NT_SUCCESS(ntStatus)) {

        D12_KdPrint (("D12TEST.SYS: Sucessfully Transferred %d Bytes\n", urb->UrbControlVendorClassRequest.TransferBufferLength));

        Irp->IoStatus.Information = (urb->UrbControlVendorClassRequest.TransferBufferLength);	// + sizeof(UCHAR));
   
    }
    
	Irp->IoStatus.Status = ntStatus;

	// free allocated urb
	ExFreePool(urb);

	D12_KdPrint(("D12TEST.SYS: exit D12_ReadWriteRegister\n"));

    return (ntStatus);
}


NTSTATUS
D12_ProcessIOCTL(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

Arguments:

    DeviceObject - pointer to the device object for this instance of the 82930
                    devcice.


Return Value:

    NT status code

--*/
{
    PIO_STACK_LOCATION irpStack;
    PVOID ioBuffer;
    ULONG inputBufferLength;
    ULONG outputBufferLength;
    PDEVICE_EXTENSION deviceExtension;
    ULONG ioControlCode;
    NTSTATUS ntStatus;
    ULONG length;
    PUCHAR pch;
    PUSB_CONFIGURATION_DESCRIPTOR configurationDescriptor;
    PUSB_DEVICE_DESCRIPTOR deviceDescriptor;

    D12_KdPrint (("D12TEST.SYS: IRP_MJ_DEVICE_CONTROL\n"));

    D12_IncrementIoCount(DeviceObject);

    //
    // Get a pointer to the current location in the Irp. This is where
    //     the function codes and parameters are located.
    //

    deviceExtension = DeviceObject->DeviceExtension;
    
    if (deviceExtension->AcceptingRequests == FALSE) {
        ntStatus = STATUS_DEVICE_DATA_ERROR;
        Irp->IoStatus.Status = ntStatus;
        Irp->IoStatus.Information = 0;

        IoCompleteRequest (Irp,
                           IO_NO_INCREMENT
                          );

        D12_DecrementIoCount(DeviceObject);                          
        return ntStatus;
    }

    irpStack = IoGetCurrentIrpStackLocation (Irp);

    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;

    ioBuffer           = Irp->AssociatedIrp.SystemBuffer;
    inputBufferLength  = irpStack->Parameters.DeviceIoControl.InputBufferLength;
    outputBufferLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;

    ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;

    //
    // Handle Ioctls from User mode
    //

    switch (ioControlCode) {

    case IOCTL_D12_RESET_PIPE:
        {
        PD12_PIPE pipe;
	    PFILE_OBJECT fileObject;

		// get our context and see if it is a pipe
	    fileObject = irpStack->FileObject;

		pipe = (PD12_PIPE) fileObject->FsContext;    

		if(pipe == NULL) {
			// error, this is not a pipe
	        ntStatus =
		        Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
		} else {            
            D12_ResetPipe(DeviceObject, pipe, TRUE);
            
            ntStatus = Irp->IoStatus.Status = STATUS_SUCCESS;
        }
        }
        break;


     case IOCTL_D12_GET_CONFIG_DESCRIPTOR:

        //
        // This api returns a copy of the configuration descriptor
        // and all endpoint/interface descriptors.
        //

        //
        // inputs  - none
        // outputs - configuration descriptor plus interface
        //          and endpoint descriptors
        //

        pch = (PUCHAR) ioBuffer;

        configurationDescriptor =
            D12_GetConfigDescriptor(DeviceObject);

        if (configurationDescriptor) {
            
            length = configurationDescriptor->wTotalLength;

            RtlCopyMemory(pch,
                          (PUCHAR) configurationDescriptor,
                          length);

            ExFreePool(configurationDescriptor);

            Irp->IoStatus.Information = length;

            ntStatus = Irp->IoStatus.Status = STATUS_SUCCESS;
        }
        else {

            Irp->IoStatus.Information = 0;

            ntStatus = Irp->IoStatus.Status = STATUS_DEVICE_DATA_ERROR;
        }

        break;


     case IOCTL_D12_RESET_DEVICE:
        
        {
        NTSTATUS ntStatus;
        ULONG portStatus;

        D12_KdPrint (("D12TEST.SYS: Reset Device Test\n"));
        
        TRAP(); // test this
        //
        // Check the port state, if it is disabled we will need 
        // to re-enable it
        //
        ntStatus = D12_GetPortStatus(DeviceObject, &portStatus);

        if (NT_SUCCESS(ntStatus) && !(portStatus & USBD_PORT_ENABLED) &&
            portStatus & USBD_PORT_CONNECTED) {
            //
            // port is disabled, attempt reset
            //
            //D12_EnableParentPort(DeviceObject);
            D12_ResetParentPort(DeviceObject);
        }

        }
        break;               

     case IOCTL_D12_GET_DEVICE_DESCRIPTOR:


        pch = (PUCHAR) ioBuffer;

        deviceDescriptor =
            deviceExtension->DeviceDescriptor;

        if (deviceDescriptor) {
            
            length = deviceDescriptor->bLength;

            RtlCopyMemory(pch,
                          (PUCHAR) deviceDescriptor,
                          length);

            Irp->IoStatus.Information = length;

            ntStatus = Irp->IoStatus.Status = STATUS_SUCCESS;
        }
        else {

            Irp->IoStatus.Information = 0;

            ntStatus = Irp->IoStatus.Status = STATUS_DEVICE_DATA_ERROR;
        }

        break;
	
	case IOCTL_READ_REGISTERS:
		D12_ReadWriteRegister(DeviceObject, Irp, FALSE);
		ntStatus = Irp->IoStatus.Status;

		break;

	case IOCTL_WRITE_REGISTERS:
		D12_ReadWriteRegister(DeviceObject, Irp, TRUE);
		ntStatus = Irp->IoStatus.Status;

		break;

	default:

        ntStatus =
            Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
    }

    IoCompleteRequest (Irp,
                       IO_NO_INCREMENT
                       );

    D12_DecrementIoCount(DeviceObject);                       

    return ntStatus;

}

